量化交易30天
本系列文章是紀錄一位量化交易新手的學習過程,除了基礎的Python語法不說明,其他金融相關的東西都會一步步地說明,希望讓更多想學習量化交易但是沒有學過相關金融知識的朋友們,透過這系列的文章,能夠對量化交易略知一二,也歡迎量化交易的高手們多多交流。
上一篇學習用Shioaji Api讀取歷史的tick數據以及分K數據,這些歷史數據的用途可以拿來做交易策略的制定,或是拿來跑機器學習的模型訓練,一旦寫好交易策略或是完成建模之後,要拿來做交易用的話,需要餵資料給策略及模型去偵測買賣訊號,而Shioaji就有提供盤中即時資料的API,本篇就來介紹一下。
import shioaji as sj
import json
# api init
api = sj.Shioaji(backend='https', simulation=False)
# login
# 這邊跟之前寫法不太一樣,這次把帳號密碼寫成一個txt檔再去讀它
with open('login.txt', 'r') as f:
kw_login = json.loads(f.read())
api.login(**kw_login)
# activate ca
person_id = kw_login['person_id']
api.activate_ca(ca_path='./Sinopac.pfx', ca_passwd=person_id, person_id=person_id)
以台積電作為範例,因為它的交易量比較大,串流數據更新得比較快
In
# contract
contract = api.Contracts.Stocks["2330"]
contract
Out
Stock(exchange=<Exchange.TSE: 'TSE'>, code='2330', symbol='TSE2330', name='台積電', category='24', limit_up=474.5, limit_down=388.5, reference=431.5, update_date='2020/09/29', margin_trading_balance=1589, short_selling_balance=208, day_trade=<DayTrade.Yes: 'Yes'>)
這邊可以修改預設接收callback訊息的處理方式,可以print出來,也可以把資料丟到策略或模型裡面,這邊就先print出來看看
# callback
@api.quote.on_quote
def quote_callback(topic: str, quote: dict):
print(f"Topic: {topic}, Quote: {quote}")
訂閱盤中資料的意思是,盤中的每一筆成交資料,server會逐筆回傳過來,而且是一成交server就會發出資料,接收端(user)這邊也會很快地收到資料,不過串流的速度會受到網路及硬體設備的影響就是了。
# subscribe tick data
api.quote.subscribe(contract, quote_type=sj.constant.QuoteType.Tick)
下面是quote_callback函數print出來的訊息,可以看到它會依照時間收資料,間隔時間是不固定的(因為有成交才會有資料),講一下各個欄位代表什麼,AmountSum:當日累積成交金額,Close:成交價,TickType:1代表外盤、2代表內盤,VolSum:當日累積成交張數,Volume:該筆tick的成交量。
Response Code: 200 | Event Code: 16 | Info: MKT/*/TSE/2330 | Event: Subscribe or Unsubscribe ok
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [2994635500.0], 'Close': [433.0], 'Date': '2020/09/29', 'TickType': [2], 'Time': '09:29:54.412402', 'VolSum': [6907], 'Volume': [1]}
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [2995069000.0], 'Close': [433.5], 'Date': '2020/09/29', 'TickType': [1], 'Time': '09:29:54.562409', 'VolSum': [6908], 'Volume': [1]}
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [2995935000.0], 'Close': [433.0], 'Date': '2020/09/29', 'TickType': [2], 'Time': '09:30:01.174475', 'VolSum': [6910], 'Volume': [2]}
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [3002004000.0], 'Close': [433.5], 'Date': '2020/09/29', 'TickType': [1], 'Time': '09:30:10.283431', 'VolSum': [6924], 'Volume': [14]}
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [3005472000.0], 'Close': [433.5], 'Date': '2020/09/29', 'TickType': [1], 'Time': '09:30:10.331148', 'VolSum': [6932], 'Volume': [8]}
Topic: MKT/idcdmzpcr01/TSE/2330, Quote: {'AmountSum': [3005905500.0], 'Close': [433.5], 'Date': '2020/09/29', 'TickType': [1], 'Time': '09:30:10.335597', 'VolSum': [6933], 'Volume': [1]}
...
因為一旦訂閱之後,它就會不斷傳資料過來,所以如果不想再收到報價,可以取消訂閱它
# unsubscribe tick data
api.quote.unsubscribe(contract, quote_type=sj.constant.QuoteType.Tick)
就像交易軟體的介面一樣,會把最新的買方最高出價的前五檔價量,賣方最低出價的前五檔價量顯示出來,可想而知,通常這個資料更新速度會是很快的,因為它會一直變化。
# subscribe Bid Ask
api.quote.subscribe(contract, quote_type=sj.constant.QuoteType.BidAsk)
如下圖,每秒會收到好幾筆資料,如果沒有需要這個資料的話,記得要取消訂閱喔,減少user跟server端的loading對大家都好~~
Response Code: 200 | Event Code: 16 | Info: QUT/*/TSE/2330 | Event: Subscribe or Unsubscribe ok
Topic: QUT/idcdmzpcr01/TSE/2330, Quote: {'AskPrice': [433.0, 433.5, 434.0, 434.5, 435.0], 'AskVolume': [100, 221, 215, 348, 806], 'BidPrice': [432.5, 432.0, 431.5, 431.0, 430.5], 'BidVolume': [181, 968, 291, 112, 91], 'Date': '2020/09/29', 'Time': '09:33:38.898983'}
Topic: QUT/idcdmzpcr01/TSE/2330, Quote: {'AskPrice': [433.0, 433.5, 434.0, 434.5, 435.0], 'AskVolume': [104, 221, 215, 348, 806], 'BidPrice': [432.5, 432.0, 431.5, 431.0, 430.5], 'BidVolume': [181, 968, 291, 112, 91], 'Date': '2020/09/29', 'Time': '09:33:39.476358'}
Topic: QUT/idcdmzpcr01/TSE/2330, Quote: {'AskPrice': [433.0, 433.5, 434.0, 434.5, 435.0], 'AskVolume': [104, 221, 215, 348, 806], 'BidPrice': [432.5, 432.0, 431.5, 431.0, 430.5], 'BidVolume': [181, 968, 291, 112, 89], 'Date': '2020/09/29', 'Time': '09:33:39.593800'}
Topic: QUT/idcdmzpcr01/TSE/2330, Quote: {'AskPrice': [433.0, 433.5, 434.0, 434.5, 435.0], 'AskVolume': [104, 221, 215, 348, 806], 'BidPrice': [432.5, 432.0, 431.5, 431.0, 430.5], 'BidVolume': [182, 968, 291, 112, 89], 'Date': '2020/09/29', 'Time': '09:33:39.646845'}
...
顧名思義,快照的意思就是某個時間點(呼叫snapshot函數的那個時間)的統計資料,生動一點的話,就像是你在用看盤軟體的時候,某個時間點的截圖。
Snapshot跟subscribe不同的地方是,呼叫一次snapshot只會抓一次當時的資料快照,而subscribe是串流資料,會不斷回傳。
In
# 可以同時訂閱多組快照
contracts = [api.Contracts.Stocks['2330'], api.Contracts.Stocks['3481']]
snapshots = api.snapshots(contracts)
snapshots
Out
[Snapshot(ts=1601372142110000000, code='2330', exchange='TSE', open=432.5, high=435.0, low=432.5, close=433.0, tick_type=<TickType.Buy: 'Buy'>, change_price=1.5, change_rate=0.35, change_type=<ChangeType.Up: 'Up'>, average_price=433.54, volume=2, total_volume=7617, amount=866000, total_amount=3302255500, yesterday_volume=34156.0, buy_price=432.5, buy_volume=148.0, sell_price=433.0, sell_volume=105, volume_ratio=0.22),
Snapshot(ts=1601372138274000000, code='3481', exchange='TSE', open=9.38, high=9.4, low=9.29, close=9.3, tick_type=<TickType.Sell: 'Sell'>, change_price=0.0, change_rate=0.0, change_type=<ChangeType.Unchanged: 'Unchanged'>, average_price=9.35, volume=1, total_volume=28674, amount=9300, total_amount=268000070, yesterday_volume=127144.0, buy_price=9.3, buy_volume=152.0, sell_price=9.31, sell_volume=162, volume_ratio=0.23)]
個人覺得snapshot功能其實蠻好用的,跟真人在看盤其實蠻像的,有想到的用法是每隔一段時間就去呼叫一下,然後把資料推進模型計算,簡單一點的話,可以使用for-loop來做
In
# 每隔10秒抓一次資料
import time
for i in range(0,4):
print(snapshots)
time.sleep(10)
Out
[Snapshot(ts=1601372142110000000, code='2330', exchange='TSE', open=432.5, high=435.0, low=432.5, close=433.0, tick_type=<TickType.Buy: 'Buy'>, change_price=1.5, change_rate=0.35, change_type=<ChangeType.Up: 'Up'>, average_price=433.54, volume=2, total_volume=7617, amount=866000, total_amount=3302255500, yesterday_volume=34156.0, buy_price=432.5, buy_volume=148.0, sell_price=433.0, sell_volume=105, volume_ratio=0.22), Snapshot(ts=1601372138274000000, code='3481', exchange='TSE', open=9.38, high=9.4, low=9.29, close=9.3, tick_type=<TickType.Sell: 'Sell'>, change_price=0.0, change_rate=0.0, change_type=<ChangeType.Unchanged: 'Unchanged'>, average_price=9.35, volume=1, total_volume=28674, amount=9300, total_amount=268000070, yesterday_volume=127144.0, buy_price=9.3, buy_volume=152.0, sell_price=9.31, sell_volume=162, volume_ratio=0.23)]
[Snapshot(ts=1601372142110000000, code='2330', exchange='TSE', open=432.5, high=435.0, low=432.5, close=433.0, tick_type=<TickType.Buy: 'Buy'>, change_price=1.5, change_rate=0.35, change_type=<ChangeType.Up: 'Up'>, average_price=433.54, volume=2, total_volume=7617, amount=866000, total_amount=3302255500, yesterday_volume=34156.0, buy_price=432.5, buy_volume=148.0, sell_price=433.0, sell_volume=105, volume_ratio=0.22), Snapshot(ts=1601372138274000000, code='3481', exchange='TSE', open=9.38, high=9.4, low=9.29, close=9.3, tick_type=<TickType.Sell: 'Sell'>, change_price=0.0, change_rate=0.0, change_type=<ChangeType.Unchanged: 'Unchanged'>, average_price=9.35, volume=1, total_volume=28674, amount=9300, total_amount=268000070, yesterday_volume=127144.0, buy_price=9.3, buy_volume=152.0, sell_price=9.31, sell_volume=162, volume_ratio=0.23)]
[Snapshot(ts=1601372142110000000, code='2330', exchange='TSE', open=432.5, high=435.0, low=432.5, close=433.0, tick_type=<TickType.Buy: 'Buy'>, change_price=1.5, change_rate=0.35, change_type=<ChangeType.Up: 'Up'>, average_price=433.54, volume=2, total_volume=7617, amount=866000, total_amount=3302255500, yesterday_volume=34156.0, buy_price=432.5, buy_volume=148.0, sell_price=433.0, sell_volume=105, volume_ratio=0.22), Snapshot(ts=1601372138274000000, code='3481', exchange='TSE', open=9.38, high=9.4, low=9.29, close=9.3, tick_type=<TickType.Sell: 'Sell'>, change_price=0.0, change_rate=0.0, change_type=<ChangeType.Unchanged: 'Unchanged'>, average_price=9.35, volume=1, total_volume=28674, amount=9300, total_amount=268000070, yesterday_volume=127144.0, buy_price=9.3, buy_volume=152.0, sell_price=9.31, sell_volume=162, volume_ratio=0.23)]
[Snapshot(ts=1601372142110000000, code='2330', exchange='TSE', open=432.5, high=435.0, low=432.5, close=433.0, tick_type=<TickType.Buy: 'Buy'>, change_price=1.5, change_rate=0.35, change_type=<ChangeType.Up: 'Up'>, average_price=433.54, volume=2, total_volume=7617, amount=866000, total_amount=3302255500, yesterday_volume=34156.0, buy_price=432.5, buy_volume=148.0, sell_price=433.0, sell_volume=105, volume_ratio=0.22), Snapshot(ts=1601372138274000000, code='3481', exchange='TSE', open=9.38, high=9.4, low=9.29, close=9.3, tick_type=<TickType.Sell: 'Sell'>, change_price=0.0, change_rate=0.0, change_type=<ChangeType.Unchanged: 'Unchanged'>, average_price=9.35, volume=1, total_volume=28674, amount=9300, total_amount=268000070, yesterday_volume=127144.0, buy_price=9.3, buy_volume=152.0, sell_price=9.31, sell_volume=162, volume_ratio=0.23)]
本篇總結
這篇串接了盤中即時資料,包含tick、BidAsk、Snapshot,有了資料也寫了策略之後,再來當然就是要實際交易拉,所以下一篇會來練習用api下單以及查詢帳戶狀況,相信也是大家會很需要的功能,請繼續收看囉~
P.S.
如果大家對於量化交易有興趣的話,我自己有上過以下這門課,課程內容從串接股市資料API、儲存至資料庫、將自己的策略轉化成程式碼、自動下單,並且可以把整個流程自動化,每天早上執行一次,一整天就不用看盤了,覺得是蠻實戰的,可以參考看看。
筆者 Sean
奈米戶投資人 / Python愛用者
喜歡用Python玩轉金融數據,從個股基本面、技術面、籌碼面相關資料,一直到總體經濟數據,都是平常接觸到的素材;對於投資,除了研究歷史數據,也喜歡瞭解市場上大家在玩些什麼。